package de.justphil.tcg.tcgserver.rest.db;

import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.ws.rs.core.Response;

import org.slf4j.Logger;

import com.hazelcast.core.Transaction;
import com.twmacinta.util.MD5;

import de.justphil.tcg.tcgserver.commons.domain.Card;
import de.justphil.tcg.tcgserver.commons.domain.CardInstance;
import de.justphil.tcg.tcgserver.commons.domain.Deck;
import de.justphil.tcg.tcgserver.commons.domain.User;
import de.justphil.tcg.tcgserver.commons.domain.Card.Name;
import de.justphil.tcg.tcgserver.commons.domain.wrappers.Credentials;
import de.justphil.tcg.tcgserver.commons.exceptions.TCGException;
import de.justphil.tcg.tcgserver.commons.grid.Grid;
import de.justphil.tcg.tcgserver.commons.i18n.Error;
import de.justphil.tcg.tcgserver.commons.management.DataManager;
import static de.justphil.tcg.tcgserver.commons.logging.TCGLogging.*;

public class UserRegistrar {
	private static final Logger log = restLogger(UserRegistrar.class);
	
	
	
	public User registerNewUser(EntityManagerFactory factory, DataManager dataManager, Credentials c) throws TCGException {
		
		log.debug("registerNewUser()");
		
		// Create EntityManager
		EntityManager em = factory.createEntityManager();
		
		// Begin JPA transaction
		em.getTransaction().begin();
		
		// Begin Hazelcast transaction
		Transaction txn = Grid.getTransaction();
		txn.begin();
		
		try {
			
			User user = doRegisterNewUser(em, dataManager, c);
			
			// Commit transactions
			txn.commit();
			em.getTransaction().commit();
			
			return user;
			
		}
		catch (TCGException e) {
			// Expected exception
			
			// Rollback transactions
			rollback(em, txn);
			
			// Rethrow this exception
			throw e;
			
		}
		catch (Throwable t) {
			// Unexpected exception
			
			// Print message and stack trace
			log.error("registerNewUser() ## Unexpected Throwable: {}", t.getMessage());
			
			t.printStackTrace();
			
			// Rollback transactions
			rollback(em, txn);
			
			// Throw TCGException
			throw new TCGException(Error.SERVER_ERROR_INTERNAL, Response.Status.INTERNAL_SERVER_ERROR);
			
		}
		finally {
			em.close();
		}
		
	}
	
	private void rollback(EntityManager em, Transaction txn) {
		txn.rollback();
		em.getTransaction().rollback();
	}
	
	private User doRegisterNewUser(EntityManager em, DataManager dataManager, Credentials c) throws TCGException {
		log.debug("doRegisterNewUser() username => {}, pass => {}", c.getUsername(), c.getPassword());
		
		User user = null;
		
		try {
			user = createUser(em, c);
		}
		catch (UnsupportedEncodingException e) {
			log.error("UnsupportedEncodingException during registerNewUser()");
			throw new TCGException(Error.SERVER_ERROR_INTERNAL, Response.Status.INTERNAL_SERVER_ERROR);
		}
		
		List<Card> starterPackCards 		= getStarterPackCards(dataManager);
		List<CardInstance> starterPackCIs 	= createStarterPackCardInstances(em, dataManager, starterPackCards, user);
		List<Deck> starterDecks				= createStarterDecks(em, dataManager, starterPackCIs, user);
		
		
		// User -> CardInstance // CardInstance -> User association
		for (CardInstance ci : starterPackCIs) {
			user.getCardInstances().add(ci);
			ci.setUserId(user.getId());
			//ci.setUser(user);
			
			dataManager.insertCardInstance(ci.getId(), ci);
		}
		
		// User -> Deck // Deck -> User association
		for (Deck deck : starterDecks) {
			user.getDecks().add(deck);
			deck.setUserId(user.getId());
			
			dataManager.insertDeck(deck.getId(), deck);
		}
		
		dataManager.insertUser(user.getId(), user);
		
		return user;
	}
	
	/* ######################################################################## */
	/* ######################################################################## */
	/* ######################################################################## */
	
	private User createUser(EntityManager em, Credentials c) throws UnsupportedEncodingException {
		log.debug("createUser()");
		
		MD5 md5 = new MD5();
	    md5.Update(c.getPassword(), null);
	    String md5Password = md5.asHex();
		
		User newUser = new User(c.getUsername(), md5Password, false);
		em.persist(newUser);
		em.flush();
		
		return newUser;
	}
	
	private List<Deck> createStarterDecks(EntityManager em, DataManager dataManager, List<CardInstance> starterPackCIs, User user) throws TCGException {
		
		log.debug("createStarterDecks()");
		
		
		List<Deck> decks = new ArrayList<Deck>();
		Deck deck = null;
		for (String deckName : Deck.Name.getStarterDecks()) {
			deck = new Deck(deckName, user.getId());
			
			// Persist Deck
			em.persist(deck);
			em.flush();
			
			decks.add(deck);
		}
		
		for (CardInstance ci : starterPackCIs) {
			deck = getDeckByName(decks, Deck.Name.getStarterDeckByCardName(dataManager.getCardById(ci.getCardId()).getName()));
			
			// CardInstance -> Deck association
			ci.getDecks().add(deck.getId());
			
			// Deck -> CardInstance association
			deck.getCardInstances().add(ci);
		}
		
		
		return decks;
	}
	
	private Deck getDeckByName(List<Deck> decks, String deckName) {
		
		log.debug("getDeckByName()");
		
		for(Deck deck : decks) {
			if (deckName.equals(deck.getName())) {
				return deck;
			}
		}
		
		return null;
	}
	
	private List<CardInstance> createStarterPackCardInstances(EntityManager em, DataManager dataManager, List<Card> starterPackCards, User user) {
		
		log.debug("createStarterPackCardInstances()");
		
		
		CardInstance ci = null;
		List<CardInstance> cardInstances = new ArrayList<CardInstance>(starterPackCards.size());
		for (Card card : starterPackCards) {
			ci = new CardInstance(0, 0, user.getId(), card.getId());
			
			// Persist CardInstance
			em.persist(ci);
			em.flush();
			
			// CardInstance -> Card association
			ci.setCardId(card.getId());
			
			// Card -> CardInstance association
			card.getCardInstances().add(ci);
			
			cardInstances.add(ci);
			
			// Update 'cards' IMap in cluster
			dataManager.updateCard(card.getId(), card);
		}
		
		
		return cardInstances;
	}
	
	private List<Card> getStarterPackCards(DataManager dataManager) {
		log.debug("getStarterPackCards()");
		
		Card bigfoot	= dataManager.getCardByName(Name.BIGFOOT);
		Card bullfrog	= dataManager.getCardByName(Name.BULLFROG);
		Card cerberus	= dataManager.getCardByName(Name.CERBERUS);
		Card elephant	= dataManager.getCardByName(Name.ELEPHANT);
		Card elfMage	= dataManager.getCardByName(Name.ELF_MAGE);
		Card goblin		= dataManager.getCardByName(Name.GOBLIN);
		Card hedgehog	= dataManager.getCardByName(Name.HEDGEHOG);
		Card magicRock	= dataManager.getCardByName(Name.MAGIC_ROCK);
		Card roadrunner = dataManager.getCardByName(Name.ROADRUNNER);
		Card snake		= dataManager.getCardByName(Name.SNAKE);
		Card wildBore	= dataManager.getCardByName(Name.WILD_BORE);
		Card wolf		= dataManager.getCardByName(Name.WOLF);
		
		List<Card> out = new ArrayList<Card>(12);
		
		out.add(bigfoot);
		out.add(bullfrog);
		out.add(cerberus);
		out.add(elephant);
		out.add(elfMage);
		out.add(goblin);
		out.add(hedgehog);
		out.add(magicRock);
		out.add(roadrunner);
		out.add(snake);
		out.add(wildBore);
		out.add(wolf);
		
		return out;
	}

}
